#include "call.h"

void do_adpm_hook(void *p);
static void adpm_deal_file_id(struct adpm_call_file_id *req, struct adpm_call_res *res)
{
  struct adpm_client_file *f;
  f = adpm_find_file(req->name);
  if(!f){
    pr_err("ERROR: %s file no found\n", __FUNCTION__);
    res->status = ADPM_file_nofound;
    return ;
  }
  res->status = ADPM_success;
  res->file_id.id = (unsigned long)f;
  adpm_pr("file id %lx\n", res->file_id.id);
}
static void adpm_deal_munmap(struct adpm_call_munmap *req, struct adpm_call_res *res)
{
  struct adpm_client_file *f;
  unsigned long vm_start, vm_len, flags;
  struct rb_node *p;
  struct adpm_page *pg;
  struct adpm_sb_info *sbi;
  pte_t pteval;
  f = (struct adpm_client_file *)req->id;
  if(!virt_addr_valid(f) || f->magic != ADPM_FILE_MAGIC){
    adpm_pr("file id isn't right\n");
    res->status = ADPM_inval;
    return ;
  }
  adpm_pr("munmap name %s\n", f->name);
  if(atomic_read(&f->mmap_cnt) == 0){
    adpm_pr("wrong: adpm file mmap cnt is zero\n");
    res->status = ADPM_inval;
    return ;
  }
  atomic_dec(&f->mmap_cnt);
  sbi = f->sbi;
  vm_start = f->vminfo.vm_start;
  vm_len = f->vminfo.vm_end - vm_start;
  spin_lock_irqsave(&f->pages_lock, flags);
  p = rb_first(&f->pages);
  while(p){
    pg = container_of(p, struct adpm_page, vm_node);
    if(pg->rmap.pt){
      pteval = native_ptep_get_and_clear(pg->rmap.pt);
      BUG_ON(pte_pfn(pteval) != adpm_page_to_real_pfn(pg, sbi));
      pg->rmap.pt = NULL;
      adpm_pr("unmap pgoff %lu\n", pg->vm_pgoff);
    }
    p = rb_next(p);
  }
  spin_unlock_irqrestore(&f->pages_lock, flags);
  adpm_pr("munmap success\n");
  res->status = ADPM_success;
}
static void adpm_deal_make_dirty(struct adpm_call_make_dirty *md, struct adpm_call_res *res)
{
  struct adpm_client_file *f = (struct adpm_client_file *)md->id;
  if(!virt_addr_valid(f) || f->magic != ADPM_FILE_MAGIC){
    res->status = ADPM_inval;
    return ;
  }
  if(atomic_read(&f->mmap_cnt) == 0 || !md->len){
    res->status = ADPM_inval;
    return ;
  }
  if(adpm_make_dirty(f, md->vir_start, md->len)){
    res->status = ADPM_other_err;
  }else{
    res->status = ADPM_success;
  }
}
static void adpm_kcall_deal(struct adpm_call_req *req, struct adpm_call_res *res)
{
  adpm_pr("kcall type %ld\n", req->type);
  barrier();
  switch(req->type){
  case ADPM_call_file_id:
      adpm_deal_file_id(&req->file_id, res);
      break;
  case ADPM_call_munmap:
    adpm_deal_munmap(&req->munmap, res);
    break;
  case ADPM_call_make_dirty:
    adpm_deal_make_dirty(&req->make_dirty, res);
    break;
  default:
    res->status = ADPM_type_undefined;
    break;
  }
}
static int adpm_call_deal(void __user *req, void __user* res)
{
  int ret;
  struct adpm_call_req *kreq;
  struct adpm_call_res *kres;
  if(!req || !res)return -EINVAL;
  kreq = kzalloc(sizeof(struct adpm_call_req), GFP_KERNEL);
  if(!kreq)return -ENOMEM;
  kres = kzalloc(sizeof(struct adpm_call_res), GFP_KERNEL);
  if(!kres){
    ret = -ENOMEM;
    goto out1;
  }
  ret = copy_from_user(kreq, req, sizeof(*kreq));
  if(ret){
    goto out2;
  }
  adpm_kcall_deal(kreq, kres);
  ret = copy_to_user(res, kres, sizeof(*kres));
out2:
  kfree(kres);
out1:
  kfree(kreq);
  return ret;
}
int adpm_call_register(void)
{
  do_adpm_hook((void *)adpm_call_deal);
  return 0;
}
void adpm_call_unregister(void)
{
  do_adpm_hook(NULL);
}
